/*
 * Copyright (c) 2019.  drakelee.base All rights reserved.
 */

package com.base.components.task.service;

import com.base.components.common.boot.SpringContextUtil;
import com.base.components.common.constants.sys.Dates;
import com.base.components.common.service.task.SysTask;
import com.base.components.task.exception.OtherServerHandledException;
import com.base.components.task.exception.TaskExecuteException;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>Description: 非阻塞唯一性作业处理类</p>
 *
 * <p>该类会对同一作业同一触发时间触发调用时进行经行隔离，
 *    比如当当前服务器获得触发的作业对象时，会对该对象经行乐观锁定，
 *    其它服务器会就此作业的此次触发时间以{@link TaskManager#lockingSleepIntervalMillisecond}的周期时间进行观察，
 *    直到当前服务器处理时间超过{@link TaskManager#lockingMaxMillisecond}或当前服务器执行完毕，
 *    其它服务器才能继续处理或结束
 *
 * <p>该类在作业执行完成时持久化对象进行操作时发生的异常情况做了一定处理，
 *    见{@link #finishAndPersistence(SysTask, DateTime, boolean, boolean)}
 *
 * @author <a href="drakelee1221@gmail.com">LiGeng</a>
 * @version 1.0.0, 2017-08-03 15:02
 *
 */
public abstract class NonBlockingUniqueLockTaskHandler extends QuartzJobBean {

  private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  protected List<String> resultMessages = Lists.newArrayList();

  protected JobExecutionContext jobExecutionContext;
  /**
   * 得到作业上下文
   */
  protected JobExecutionContext getJobExecutionContext() {
    return this.jobExecutionContext;
  }

  /**
   * 设置作业执行结果
   *
   * @param resultMessage
   */
  protected void setResultMessage(String resultMessage) {
    resultMessages.clear();
    resultMessages.add(resultMessage);
  }

  /**
   * 追加作业执行结果
   *
   * @param resultMessage
   */
  protected void addResultMessage(String resultMessage) {
    resultMessages.add(resultMessage);
  }

  protected String getResultMessage() {
    return StringUtils.join(resultMessages, ", ");
  }

  protected <T> T getBean(Class<T> clazz) {
    return SpringContextUtil.getBean(clazz);
  }


  /**
   * 检测当前任务是否已经完成
   *
   * @param task        当前任务实体
   * @param currentDate 触发当前任务的时间
   * @return true=已完成；false=未完成
   */
  private boolean checkTaskIsDoing(SysTask task, DateTime currentDate) {
    if (task.getLastTime() != null) {
      if (currentDate.isEqual(new DateTime(task.getLastTime()))) {
        return true;
      }
    }
    return false;
  }

  private void finishAndPersistence(SysTask task, DateTime currentDate, boolean setDisabled,
                                    boolean doneTask) {
    SysTaskService sysTaskService = getBean(SysTaskService.class);
    sysTaskService.finishAndPersistence(task, currentDate, setDisabled, doneTask);

  }

  private SysTask findTask(String id) {
    SysTaskService sysTaskService = getBean(SysTaskService.class);
    return sysTaskService.findAndLock(id);
  }

  @Override
  protected final void executeInternal(JobExecutionContext jobExecutionContext)
    throws JobExecutionException {
    DateTime currentDate =
        new DateTime(jobExecutionContext.getFireTime()).millisOfSecond().setCopy(0);
    this.jobExecutionContext = jobExecutionContext;
    TaskManager taskManager = getBean(TaskManager.class);
    String taskId = jobExecutionContext.getJobDetail().getKey().getName();
    Throwable ex = null;
    SysTask task = null;
    boolean setDisabled = false;
    //是否真正的执行了作业
    boolean doneTask = false;
    try {
      task = findTask(taskId);
      if (task == null || task.getDisabled()) {
        taskManager.removeJob(taskId, false);
        ex = new TaskExecuteException(task == null ? "未找到作业，ID：" + taskId : "作业已经暂停，ID：" + taskId);
      }
      //判断当前时间和被查询到的作业任务最后完成时间是否相同，相同则说明其他服务器已经执行过任务了
      else if (checkTaskIsDoing(task, currentDate)) {
        String executedTime = new DateTime(task.getLastTime()).toString(Dates.DATE_TIME_FORMATTER_PATTERN);
        logger.info("调度任务已被其它节点执行（{}，{}）：{}", executedTime, task.getLastServer(), task);
        ex = new OtherServerHandledException(
            "调度任务已被其它节点执行（" + executedTime + "，" + task.getLastServer() + "）：" + task);
      }
      //执行作业
      else {
        logger.info("调度任务开始（{}）：{}", currentDate.toString(Dates.DATE_TIME_FORMATTER_PATTERN), task);

        execute(new HashMap<>(jobExecutionContext.getMergedJobDataMap()));

        if (jobExecutionContext.getNextFireTime() == null) {
          JobDataMap argMap = jobExecutionContext.getMergedJobDataMap();
          if(argMap != null && !Boolean.TRUE.equals(argMap.get(TaskManager.TRIGGER_TASK_KEY))){
            taskManager.removeJob(task.getId(), false);
            setDisabled = true;
          }
        }
        logger.info("调度任务结束（{}）：{}", DateTime.now().toString(Dates.DATE_TIME_FORMATTER_PATTERN), task);
        doneTask = true;
      }
    }
    catch (Throwable e) {
      logger.error("调度任务异常（{"+DateTime.now().toString(Dates.DATE_TIME_FORMATTER_PATTERN)+"}）："+task, e);
      ex = e;
    }
    finally {
      try {
        //完成作业
        finishAndPersistence(task, currentDate, setDisabled, doneTask);
        //写执行日志
        getBean(SysTaskLogService.class).addTaskLog(taskId, getResultMessage(), ex, currentDate.toDate());
      }
      finally {
        TaskManager.removeDoingTask(taskId);
      }
    }
  }

  /**
   * 作业执行方法
   * @param argMap 作业持久化记录中的 arguments_map 传递过来的参数
   */
  protected abstract void execute(Map<String, Object> argMap);

}
